using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace WPCoco.Graphics
{
    public class AtlasSprite : IGraphicState
    {
        public AtlasSprite(IAtlasSpriteClient owner, Texture2D texture)
        {
            if (owner == null) throw new NullReferenceException("owner");
            this.Owner = owner;
            owner.Atlas = this;
            CreateBuffer();
            CreateBasicEffect();
            owner.RequestRegister(this, null);
        }

        public AtlasSprite(IAtlasSpriteClient owner)
            : this(owner, owner.Texture)
        {

        }

        #region BasicEffect

        BasicEffect basicEffect;

        void CreateBasicEffect() 
        {
            rasterizer = new RasterizerState();
            rasterizer.CullMode = CullMode.None;
            rasterizer.MultiSampleAntiAlias = false;

            blend = BlendState.AlphaBlend;

            depthStencil = DepthStencilState.Default;

            sampler = SamplerState.LinearClamp;

            basicEffect = new BasicEffect(Screen.CurrentGraphicsDevice);
            basicEffect.VertexColorEnabled = true;
            basicEffect.TextureEnabled = true;
            basicEffect.Projection = Matrix.CreateOrthographicOffCenter(1, Screen.Width,     // left, right
                                                                        Screen.Height, 1,    // bottom, top
                                                                        0, 1);
            basicEffect.World = Matrix.Identity;
        }
        
        #endregion

        public Matrix BaseWorldMatrix 
        {
            get { return basicEffect.World; }
            set { basicEffect.World = value; }
        }

        WorldMatrix worldMatrix = new WorldMatrix();

        public WorldMatrix WorldMatrix
        {
            get { return worldMatrix; }
        }

        IAtlasSpriteClient owner;

        public IAtlasSpriteClient Owner
        {
            get { return owner; }
            private set { owner = value; }
        }

        public Texture2D Texture 
        {
            get 
            {
                return owner.Texture;
            } 
        }

        void CreateBuffer() 
        {
            quads = new VertexPositionColorTexture[size * 6];
        }

        void GrowBuffer()
        {
            VertexPositionColorTexture[] old = quads;
            size *= 2;
            CreateBuffer();
            Array.Copy(old, quads, old.Length);
        }

        int size = 1;

        List<IAtlasSpriteClient> clients = new List<IAtlasSpriteClient>();

        VertexPositionColorTexture[] quads = null; 

        public void Register(IAtlasSpriteClient client) 
        {
            Register(client, true);
        }

        public void Register(IAtlasSpriteClient client, bool recursive) 
        {
            if (!client.CanRegister()) 
                throw new InvalidOperationException("Cant register this client");

            if (client.Texture != Texture && client.Texture!=null) throw new InvalidOperationException("Texture is not the same as AtlasSprite");
            
            if (clients.Count == size) 
                GrowBuffer();

            int index = GetClientIndex(client);
            if (index == -1)
                throw new InvalidOperationException("Index is -1");
            Array.Copy(quads, index * 6, quads, index * 6 + 6, (clients.Count - index) * 6);
            client.AtlasIndex = index;
            client.Atlas = this;
            client.ReportChange(QuadDirtyComponent.All);
            clients.Insert(index, client);
            for (int i = index + 1; i < clients.Count; i++)
                clients[i].AtlasIndex++;
            if (recursive) 
            {
                foreach (var c in client.GetContainerEnumerator())
                    c.RequestRegister(this, client);                  
            }
        }

        public void Unregister(IAtlasSpriteClient client)
        {
            if (client == null || client.AtlasIndex == -1) return;
            IAtlasSpriteClient min, max;
            GetExtremeClients(client, out min, out max);
            int dif = max.AtlasIndex - min.AtlasIndex + 1;
            int minIndex = min.AtlasIndex;
            int maxIndex = max.AtlasIndex;
            int count = clients.Count * 6 - (6 * maxIndex + 6);
            Array.Copy(quads, 6 * maxIndex + 6, quads, 6 * minIndex, count);
            for (int i = minIndex; i <= maxIndex; i++)
                clients[i].AtlasIndex = -1;
            clients.RemoveRange(minIndex, dif);         
            for (int i = minIndex; i < clients.Count; i++)
                clients[i].AtlasIndex -= dif;
        }

        /// <summary>
        /// To obtain extreme nodes in the client branch allready registered in the atlas
        /// </summary>
        void GetExtremeClients(IAtlasSpriteClient client, out IAtlasSpriteClient min, out IAtlasSpriteClient max) 
        {
            min = client; max = client;
            IAtlasSpriteClient temp;
            do
            {
                temp = null;
                foreach (var node in min.GetContainerEnumerator())
                {
                    if (node.IsAtlasLinked())
                    {
                        min = temp = node;
                        break;
                    }
                }
            } while (temp != null);
            do
            {
                temp = null;
                foreach (var node in max.GetReverseContainerEnumerator())
                {
                    if (node.IsAtlasLinked())
                    {
                        max = temp = node;
                        break;
                    }
                }
            } while (temp != null);
            if (client.AtlasIndex < min.AtlasIndex)
                min = client;
            if (client.AtlasIndex > max.AtlasIndex)
                max = client;
        }

        /// <summary>
        /// Calculating the correct Index for an new Client 
        /// </summary>
        int GetClientIndex(IAtlasSpriteClient client) 
        {
            if (client.IsAtlasLinked()) return -1;
            if (owner == client) return 0;

            IAtlasSpriteClient parent = client.ParentClient;
            IAtlasSpriteClient sibling = parent.GetNextNodeLinked(client);
            IAtlasSpriteClient min, max;
            if (sibling == null) 
            {
                GetExtremeClients(client.ParentClient, out min, out max);
                return max.AtlasIndex + 1;
            }
            GetExtremeClients(sibling, out min, out max);
            return min.AtlasIndex;
        }

        public void Update(IAtlasSpriteClient client)
        {
            AtlasQuad aquad = client.GetQuad();
            int index = client.AtlasIndex * 6;
            quads[index + 0] = aquad.vertexs[0];
            quads[index + 1] = aquad.vertexs[1];
            quads[index + 2] = aquad.vertexs[2];
            quads[index + 3] = aquad.vertexs[0];
            quads[index + 4] = aquad.vertexs[2];
            quads[index + 5] = aquad.vertexs[3];
        }

        public void Draw() 
        {
            RasterizerState rlast = Screen.CurrentGraphicsDevice.RasterizerState;
            Screen.CurrentGraphicsDevice.RasterizerState = rasterizer;
            BlendState blast = Screen.CurrentGraphicsDevice.BlendState;
            Screen.CurrentGraphicsDevice.BlendState = blend;
            DepthStencilState dlast = Screen.CurrentGraphicsDevice.DepthStencilState;
            Screen.CurrentGraphicsDevice.DepthStencilState = depthStencil;

            basicEffect.Texture = Texture;

            SamplerState slast = Screen.CurrentGraphicsDevice.SamplerStates[0];
            Screen.CurrentGraphicsDevice.SamplerStates[0] = sampler;

            for (int i = 0; i < basicEffect.CurrentTechnique.Passes.Count; i++)
            {
                EffectPass pass = basicEffect.CurrentTechnique.Passes[i];

                pass.Apply();
                Screen.CurrentGraphicsDevice.DrawUserPrimitives<VertexPositionColorTexture>(PrimitiveType.TriangleList,
                                                                                            quads,
                                                                                            0,
                                                                                            clients.Count * 2);
            }

            Screen.CurrentGraphicsDevice.RasterizerState = rlast;
            Screen.CurrentGraphicsDevice.BlendState = blast;
            Screen.CurrentGraphicsDevice.DepthStencilState = dlast;
            Screen.CurrentGraphicsDevice.SamplerStates[0] = slast;
        }

        #region IGraphicState Members

        SamplerState sampler = SamplerState.LinearClamp;

        public SamplerState Sampler
        {
            get { return sampler; }
            set { sampler = value; }
        }

        DepthStencilState depthStencil;

        public DepthStencilState DepthStencil
        {
            get { return depthStencil; }
            set { depthStencil = value; }
        }

        BlendState blend;

        public BlendState Blend
        {
            get { return blend; }
            set { blend = value; }
        }

        RasterizerState rasterizer;

        public RasterizerState Rasterizer
        {
            get { return rasterizer; }
            set { rasterizer = value; }
        }

        #endregion
    }
}
